const { BackupSetting, Backup } = require('../models');
const path = require('path');
const fs = require('fs');
const { spawn, execSync } = require('child_process');
const moment = require('moment');
const config = require('../config/config');
const cron = require('node-cron');
const logger = require('../config/logger');
const mongoose = require('mongoose');

/**
 * 检查MongoDB工具是否可用
 * @returns {boolean} 如果MongoDB工具可用，返回true
 */
const checkMongoDBTools = () => {
  try {
    // 尝试执行一个简单的mongodump命令，检查是否安装
    execSync('mongodump --version', { stdio: 'ignore' });
    return true;
  } catch (error) {
    logger.warn('MongoDB数据库工具(mongodump/mongorestore)未安装或不可用，将使用替代方案');
    return false;
  }
};

/**
 * 使用Node.js直接从MongoDB导出数据
 * @param {string} outputPath - 输出文件路径
 * @returns {Promise<number>} 文件大小（KB）
 */
const exportMongoDBData = async (outputPath) => {
  try {
    // 获取所有集合
    const collections = await mongoose.connection.db.collections();
    
    // 创建导出数据对象
    const exportData = {
      metadata: {
        exportDate: new Date(),
        version: '1.0',
        database: mongoose.connection.name
      },
      collections: {}
    };
    
    // 对每个集合导出数据
    for (const collection of collections) {
      const collectionName = collection.collectionName;
      logger.info(`导出集合: ${collectionName}`);
      
      // 获取集合中的所有文档
      const documents = await collection.find({}).toArray();
      
      exportData.collections[collectionName] = documents;
    }
    
    // 写入到文件
    const jsonData = JSON.stringify(exportData, null, 2);
    fs.writeFileSync(outputPath, jsonData);
    
    // 计算文件大小（KB）
    const stats = fs.statSync(outputPath);
    const fileSizeInKB = Math.ceil(stats.size / 1024);
    
    return fileSizeInKB;
  } catch (error) {
    logger.error('导出数据失败:', error);
    throw error;
  }
};

/**
 * 使用Node.js直接恢复MongoDB数据
 * @param {string} inputPath - 输入文件路径
 * @returns {Promise<void>}
 */
const importMongoDBData = async (inputPath) => {
  try {
    // 读取备份文件
    const jsonData = fs.readFileSync(inputPath, 'utf8');
    const importData = JSON.parse(jsonData);
    
    if (!importData.collections) {
      throw new Error('无效的备份文件格式');
    }
    
    logger.info(`开始恢复数据库: ${importData.metadata?.database || 'unknown'}`);
    
    // 恢复每个集合的数据
    for (const [collectionName, documents] of Object.entries(importData.collections)) {
      if (documents.length === 0) {
        logger.info(`跳过空集合: ${collectionName}`);
        continue;
      }
      
      logger.info(`恢复集合: ${collectionName} (${documents.length} 文档)`);
      
      // 获取集合实例
      const collection = mongoose.connection.db.collection(collectionName);
      
      // 删除现有数据
      await collection.deleteMany({});
      
      // 导入文档
      if (documents.length > 0) {
        await collection.insertMany(documents);
      }
    }
    
    logger.info('数据库恢复完成');
  } catch (error) {
    logger.error('导入数据失败:', error);
    throw error;
  }
};

/**
 * 执行数据库备份
 * @param {string} type - 备份类型，'auto'或'manual'
 * @returns {Promise<object>} 备份记录对象
 */
const executeBackup = async (type = 'auto') => {
  // 创建备份文件名
  const timestamp = moment().format('YYYY-MM-DD_HH-mm-ss');
  const mongoToolsAvailable = checkMongoDBTools();
  const fileExt = mongoToolsAvailable ? 'zip' : 'json';
  const backupFileName = `${type}_${timestamp}.${fileExt}`;
  const relativePath = `/backups/${backupFileName}`;
  const fullPath = path.join(__dirname, '../../', relativePath);
  
  // 确保备份目录存在
  const backupDir = path.dirname(fullPath);
  if (!fs.existsSync(backupDir)) {
    fs.mkdirSync(backupDir, { recursive: true });
  }
  
  // 创建备份记录
  const backup = await Backup.create({
    type,
    fileSize: 0,
    status: 'in_progress',
    filePath: relativePath,
  });
  
  // 执行实际的备份操作
  try {
    // 根据是否安装了MongoDB工具选择备份方法
    if (mongoToolsAvailable) {
      // 从配置中获取MongoDB连接信息
      const { url } = config.mongoose;
      
      // 构建mongodump命令参数
      const mongodumpArgs = [
        '--uri', url,
        '--archive=' + fullPath,
        '--gzip'
      ];
      
      // 执行mongodump命令
      const mongodump = spawn('mongodump', mongodumpArgs);
      
      let stdoutData = '';
      let stderrData = '';
      
      mongodump.stdout.on('data', (data) => {
        stdoutData += data.toString();
      });
      
      mongodump.stderr.on('data', (data) => {
        stderrData += data.toString();
      });
      
      return new Promise((resolve, reject) => {
        mongodump.on('close', async (code) => {
          try {
            if (code === 0) {
              // 备份成功
              const stats = fs.statSync(fullPath);
              const fileSize = Math.ceil(stats.size / 1024); // 转换为KB
              
              // 更新备份记录
              backup.fileSize = fileSize;
              backup.status = 'success';
              await backup.save();
              
              logger.info(`数据库备份成功: ${fullPath}, 大小: ${fileSize}KB`);
              resolve(backup);
            } else {
              // 备份失败
              backup.status = 'failed';
              backup.errorMessage = stderrData || '未知错误';
              await backup.save();
              
              logger.error(`数据库备份失败: ${stderrData}`);
              reject(new Error(stderrData || '备份失败'));
            }
          } catch (error) {
            logger.error('更新备份状态失败:', error);
            reject(error);
          }
        });
        
        mongodump.on('error', async (error) => {
          try {
            // 处理命令执行错误
            backup.status = 'failed';
            backup.errorMessage = `执行mongodump命令失败: ${error.message}`;
            await backup.save();
            
            logger.error(`执行mongodump命令失败: ${error.message}`);
            reject(error);
          } catch (saveError) {
            logger.error('更新备份状态失败:', saveError);
            reject(saveError);
          }
        });
        
        // 设置超时处理
        setTimeout(async () => {
          try {
            // 检查备份是否仍在进行中
            const currentBackup = await Backup.findById(backup._id);
            if (currentBackup && currentBackup.status === 'in_progress') {
              currentBackup.status = 'failed';
              currentBackup.errorMessage = '备份操作超时';
              await currentBackup.save();
              
              logger.error(`数据库备份超时: ${fullPath}`);
              
              // 如果进程还在运行，则终止它
              if (!mongodump.killed) {
                mongodump.kill();
              }
              
              reject(new Error('备份操作超时'));
            }
          } catch (error) {
            logger.error('检查备份状态失败:', error);
            reject(error);
          }
        }, 5 * 60 * 1000); // 5分钟超时
      });
    } else {
      // 使用Node.js直接从MongoDB导出数据
      try {
        logger.info(`开始使用内置方法导出数据库: ${fullPath}`);
        const fileSize = await exportMongoDBData(fullPath);
        
        // 更新备份记录
        backup.fileSize = fileSize;
        backup.status = 'success';
        await backup.save();
        
        logger.info(`数据库备份成功(内置方法): ${fullPath}, 大小: ${fileSize}KB`);
        return backup;
      } catch (error) {
        // 备份失败
        backup.status = 'failed';
        backup.errorMessage = error.message || '内置备份方法失败';
        await backup.save();
        
        logger.error(`数据库备份失败(内置方法): ${error.message}`);
        throw error;
      }
    }
  } catch (error) {
    // 处理异常
    if (backup.status !== 'failed') {
      backup.status = 'failed';
      backup.errorMessage = error.message || '执行备份命令时出错';
      await backup.save();
    }
    
    logger.error('执行备份命令时出错:', error);
    throw error;
  }
};

/**
 * 恢复数据库
 * @param {string} backupPath - 备份文件路径
 * @returns {Promise<boolean>} 恢复成功返回true
 */
const restoreDatabase = async (backupPath) => {
  const mongoToolsAvailable = checkMongoDBTools();
  
  try {
    if (mongoToolsAvailable) {
      // 使用mongorestore命令恢复
      const { url } = config.mongoose;
      
      // 构建mongorestore命令参数
      const mongorestoreArgs = [
        '--uri', url,
        '--gzip',
        '--archive=' + backupPath,
        '--drop'  // 恢复前删除现有数据
      ];
      
      logger.info(`开始恢复数据库: ${backupPath}`);
      
      return new Promise((resolve, reject) => {
        const mongorestore = spawn('mongorestore', mongorestoreArgs);
        
        let stdoutData = '';
        let stderrData = '';
        
        mongorestore.stdout.on('data', (data) => {
          stdoutData += data.toString();
        });
        
        mongorestore.stderr.on('data', (data) => {
          stderrData += data.toString();
        });
        
        mongorestore.on('close', (code) => {
          if (code === 0) {
            logger.info(`数据库恢复成功: ${backupPath}`);
            resolve(true);
          } else {
            logger.error(`数据库恢复失败: ${stderrData}`);
            reject(new Error(stderrData || '恢复失败'));
          }
        });
        
        mongorestore.on('error', (error) => {
          logger.error(`执行mongorestore命令失败: ${error.message}`);
          reject(error);
        });
        
        // 设置超时
        setTimeout(() => {
          if (!mongorestore.killed) {
            mongorestore.kill();
            reject(new Error('恢复操作超时'));
          }
        }, 10 * 60 * 1000); // 10分钟超时
      });
    } else {
      // 使用内置方法恢复
      logger.info(`开始使用内置方法恢复数据库: ${backupPath}`);
      await importMongoDBData(backupPath);
      return true;
    }
  } catch (error) {
    logger.error(`恢复数据库失败: ${error.message}`);
    throw error;
  }
};

/**
 * 执行自动备份
 * @returns {Promise<void>}
 */
const executeAutoBackup = async () => {
  try {
    // 获取备份设置
    const backupSetting = await BackupSetting.findOne({ isDeleted: false });
    if (!backupSetting || !backupSetting.autoBackup) {
      logger.info('自动备份已禁用或未配置');
      return;
    }
    
    logger.info(`开始执行自动备份，频率: ${backupSetting.frequency}`);
    
    // 执行备份
    const backup = await executeBackup('auto');
    
    // 清理旧备份
    await cleanupOldBackups(backupSetting.retentionCount);
    
    logger.info(`自动备份完成，ID: ${backup._id}`);
  } catch (error) {
    logger.error('自动备份失败:', error);
  }
};

/**
 * 清理旧备份
 * @param {number} retentionCount - 保留的备份数量
 * @returns {Promise<void>}
 */
const cleanupOldBackups = async (retentionCount) => {
  try {
    // 获取所有备份，按照时间排序
    const backups = await Backup.find({
      isDeleted: false,
      status: 'success',
      type: 'auto' // 只清理自动备份
    }).sort({ date: -1 });
    
    // 如果备份数量超过保留数量，则删除最旧的备份
    if (backups.length > retentionCount) {
      const backupsToDelete = backups.slice(retentionCount);
      
      for (const backup of backupsToDelete) {
        // 标记备份记录为已删除
        backup.isDeleted = true;
        await backup.save();
        
        // 删除备份文件
        const fullPath = path.join(__dirname, '../../', backup.filePath);
        if (fs.existsSync(fullPath)) {
          fs.unlinkSync(fullPath);
          logger.info(`已删除旧备份文件: ${fullPath}`);
        }
      }
      
      logger.info(`已清理 ${backupsToDelete.length} 个旧备份`);
    }
  } catch (error) {
    logger.error('清理旧备份失败:', error);
  }
};

/**
 * 启动自动备份调度器
 */
const startBackupScheduler = async () => {
  try {
    // 获取备份设置
    let backupSetting = await BackupSetting.findOne({ isDeleted: false });
    
    // 如果不存在，则创建默认设置
    if (!backupSetting) {
      backupSetting = await BackupSetting.create({});
    }
    
    if (!backupSetting.autoBackup) {
      logger.info('自动备份已禁用，不启动调度器');
      return;
    }
    
    // 配置调度表达式
    let cronExpression;
    const [hour, minute] = backupSetting.backupTime.split(':').map(Number);
    
    switch (backupSetting.frequency) {
      case 'daily':
        cronExpression = `${minute} ${hour} * * *`;
        break;
      case 'weekly':
        cronExpression = `${minute} ${hour} * * 0`; // 每周日
        break;
      case 'biweekly':
        cronExpression = `${minute} ${hour} 1,15 * *`; // 每月1日和15日
        break;
      case 'monthly':
        cronExpression = `${minute} ${hour} 1 * *`; // 每月1日
        break;
      default:
        cronExpression = `${minute} ${hour} * * 0`; // 默认每周
    }
    
    logger.info(`正在启动自动备份调度器，调度表达式: ${cronExpression}`);
    
    // 启动Cron任务
    const task = cron.schedule(cronExpression, () => {
      executeAutoBackup().catch(error => {
        logger.error('执行自动备份失败:', error);
      });
    });
    
    // 也可以立即执行一次备份，验证功能
    // executeAutoBackup().catch(error => {
    //   logger.error('初始自动备份失败:', error);
    // });
    
    return task;
  } catch (error) {
    logger.error('启动自动备份调度器失败:', error);
  }
};

module.exports = {
  executeBackup,
  executeAutoBackup,
  cleanupOldBackups,
  startBackupScheduler,
  restoreDatabase,
  checkMongoDBTools
}; 